所有 Class 的儲存屬性 (包括從父類繼承的任何屬性ㄉ) 都必須在初始化的期間分配一個初始值。Swift 為 Class 類型定義了兩種初始化器,去幫助確認所有的儲存屬性接收一個初始值。這些被稱為指定初始器 (designated initializers) 和便捷初始化器 (convenience initializers)。
指定初始化器是 class 中主要的初始化器,指定初始化器完全初始化由該 class 引用的所有屬性,並且調用合適的父類初始化器去繼續初始化程序給父類鏈。 class 通常只有很少數的指定初始化器,而一個 class 通常只有一個指定初始化器,指定初始化器是初始化開始並持續初始化過程到父類鏈的“管道”點。
便捷初始化器是次要支持 Class 的初始化器,你可以在相同的 class 裡定義一個便捷初始化器來調用一個指定的初始化器作為便捷初始化器來給指定初始化器設置默認參數,你也可以為具體的使用情況或輸入的值類型定義一個便捷初始化器從而創建這個 class 的實例。
class 的指定初始化器的寫法與簡單的值類型初始化器相同:
init(parameters) {
statements
}
便捷初始化器的也是一樣的寫法,但是在 init 關鍵字之前放了 convenience 修飾符,並用一個空格分隔:
convenience init(parameters) {
statements
}
為了簡化指定初始化器與便捷初始化器之間的關係,Swift 在初始化器之間委託調用有下面三個規則:
用一個簡單的方式記住這些:
我們用圖片來描述上述這些規則:
圖中的父類有一個指定初始化器和兩個便捷初始化器。一個便捷初始化器調用另一個便捷初始化器,而後者又調用了指定初始化器。這滿足了上邊的規則2、3,由於父類本身沒有其他父類,所以不需要規則1。
圖中的子類有兩個指定初始化器和一個便捷初始化器。這個便捷初始化器必須調用兩個指定初始化器其中一個,因為他只能中相同 class 中調用初始化器,目前到這邊它符合了規則2、3,接著這兩個指定初始化器又必須從父類調用一個指定初始化器,滿足規則1的要求。
這些規則不會影響到使用者如何創建每個 class 的實例,上圖中任何的初始化器都可以用來創建它們所屬 class 完全初始化的實例。這些規則只會影響你如何編寫的初始化器的實現。
下面我們用一張更複雜的 class 的結構圖,它演示了指定初始化器是如何在此層級結構中充當”管道”作用,在 class 的初始化鏈上簡化了 class 之間的內部關係:
Swift 中的 Class 初始化是一個兩階段的過程。第一階段中,每個儲存的屬性被引入類為分配了一個初始值。一旦確定了每個存儲屬性的初始狀態,那麼第二階段就開始了,每個 class 都有機會在新的實例準備使用之前來自定義它的存儲屬性。
使用兩階段初始化能使初始化安全,同時仍然為 class 層次結構中的每個 class 提供完全的靈活性。兩階段初始化防止在初始化之前訪問屬性值,也防止其他初始化器意外的將屬性值設為不同的值。
Swift 的編譯器執行四個有用的安全檢查(safety check),來確保兩階段初始化完成沒有錯誤:
基於上面的四個安全檢查,下面說明兩階段內初始化過程:
*階段一
*階段二
下圖是第一階段如何查詢假設的子類和父類的初始化調用:
圖中,初始化過程從一個子類的便捷初始化器開始。這時便捷初始化器還不能修改任何屬性。它委託給了同一 class 裡的指定初始化器。
指定初始化器確保所有的子類屬性都有一個值,然後它調用父類的指定初始化器來沿著初始化器鏈一直往上完成父類的初始化過程。
父類的指定初始化器確保所有的父類屬性都有值。由於沒有更多的父類來初始化,也就不需要更多的委託。
一旦父類中所有屬性都有初始值,它的內存就被認為完全初始化了,第一階段完成。
下圖是第二階段的初始化過程:
此時,父類的指定初始化器有機會進一步自定義實例(儘管不必要)。
一旦父類的指定初始化器完成了調用,子類的指定初始化器就可以執行額外的定制(同樣,儘管沒有這種必要)。
最後,一旦子類的指定初始化器完成,最初調用的便捷初始化器將會執行額外的自定義操作。
如果你想要自定義的子類提供一個或多個相同的初始化作為他的父類,你可以在子類中提供這些初始化的自定義實現。
當你編寫一個子類的初始化器與一個父類指定初始化器相匹配時,你正有效的提供對指定的初始化器重寫。因此,你必須在子類的初始化程序定義之前編寫 override 修飾符。即使是自動提供的默認初始化器你也可以重寫。
與重寫屬性、方法、下標一樣,override修飾符會提示 Swift 檢查父類是否有一個匹配的指定初始值設定項被重寫,並驗證你的重寫初始化器的參數是否按照預期指定了。
當重寫父類指定初始化器時,你必須寫 override 修飾符,即使子類的初始化器的實現是一個便捷初始化器。
相反的,如果你編寫一個父類便捷初始化器相匹配的子類初始化器,那麼父類的初始化器不能由你的子類直接調用。因此你的子類不是提供父類的初始化的重寫,所以在提供父類的便捷初始化器的匹配實現時,不需要 override 修飾符。
我們下面創建一個名為 Animals 的基類(base class),宣告一個名為 leg 的儲存屬性,類型為 Int ,默認值為 0。之後 leg 透過 legOfAnimal 的計算屬性來創建一個 String 類型的字符串描述:
class Animals {
var leg = 0
var legOfAnimal:String {
return "有\(leg)隻腳"
}
}
Animals 這個 class 只為它的存儲屬性提供了默認值,並且沒有提供自定義的初始化器。因此,它會自動收到一個默認初始化器。默認初始化器(如果可用)指的總是 class 的指定初始化器,也可以用來創建一個新的 Animals 實例, leg 默認為 0 :
下面我們定義了一個名為 Bird 繼承 Animal 的子類:
class Bird:Animals {
override init() {
super.init()
leg = 2
}
}
Bird 定義了一個自定義初始化器 init()。這個指定初始化器和 Bird 的父類的指定初始化器相匹配,所以 Bird 中的指定初始化器需要加上 override 修飾符。
Bird 類的 init() 初始化器以調用 super.init () 開始,這個方法作用是調用父類的初始化器。這樣可以確保 Bird 在修改屬性之前它所繼承的屬性 leg 能被 Animals 類初始化。在調用 super.init() 之後,一開始的 leg 值會被我們設置的值 2 給替換。
子類可以在初始化時修改繼承的變量屬性,但是不能修改繼承過來的常量屬性。
默認情況下,子類不會繼承其父類的初始化器。在特定的條件下父類初始化器是可以被自動繼承的。實際上,這意味著在許多狀況下你不必重寫父類初始化器,只要可以安全操作,你就可以毫不費力地繼承父類的初始化器。
假如你為在子類中引入的任何新屬性提供默認值,則遵循下面兩條規則:
*規則一
如果你的子類沒有定義任何指定初始化器,它將自動繼承所有父類的初始化器。
*規則二
如果你的子類提供所有父類指定初始化器的實現,他會按照規則一繼承它們,或者它會提供一個字定義實現作為其定義的一部分,那麼它將會自動繼承所有父類便捷初始化器。就算你的子類添加了更多的便捷初始化器,這些規則依然適用。
下面我們創建一個 Stationery 文具的基類,我們引入了一個名為 name 的 String 屬性,還提供了兩個創建 Stationery 實例的初始化器:
class Stationery{
var name: String
init(name: String) {
self.name = name
}
convenience init(){
self.init(name: "未知")
}
}
因為這個 class 沒有默認成員初始化器,所以 class 提供了一個接受單一參數的指定初始化器叫做 name 。使用這個初始化器可以使用一個具體的名稱來創建新的 Stationery 實例,我們可以提供他參數傳值到其中,又或者使用默認的便捷初始化提供的默認值表示:
接下來我們在創建一個 Tool 的類,繼承我們的 Stationery,引入了名為 quantity 的屬性,類型為Int(以及從 Stationery 繼承過來的 name 屬性),並且定義了兩個初始化器來創建 Tool 實例:
class Tool:Stationery {
var quantity:Int
init(name:String , quantity:Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
這個 class 只有一個指定初始化器,init(name:String , quantity:Int),它可以用來輸入新的 Tool 實例中所有的屬性。這個初始化器一開始先將傳入的 quantity 實際參數賦值給 quantity 屬性,這個屬性也是唯一一個通過 Tool 引入的新屬性。然後,初始化器將向上委託給父類 Stationery 的 init(name: name) 初始化器。
我們同樣定義了一個便捷初始化器,init(name: String),它只透過 name 來創建 Tool 的實例。這個便捷初始化器假設 Tool 中沒有設定數量實例的 quantity 值都為 1 。便捷初始化器的定義讓 Tool 實例創建的更方便更快速,並且當創建多個單數實例時可以避免代碼冗長。 Tool 類提供的 init(name: String) 便捷初始化器接收與 Stationery 中的指定初始化器 init(name: String) 為相同的形式參數。因為這個便捷初始化器從它的父類重寫了一個指定初始化器,所以我們必須加上 override 修飾符。
儘管 Tool 提供了 init(name: String) 初始化器作為一個便捷初始化器,然而 Tool 類為所有的父類指定初始化器提供了實現。因此,Tool 類也自動繼承了父類所有的便捷初始化器。
在這個範例中, Tool 的父類是 Stationery ,它只有一個 init()便捷初始化器。因此這個初始化器也被 Tool 類繼承。這個繼承的 init() 函數和 Stationery 提供的是一樣的,除了它是委託給 Tool 版本的 init(name: String) 而不是 Stationery 版本。
我們可以用下面三種方式依照你的需求創建不同的實例:
let tool1 = Tool()
// name = 未知 , 數量為默認值為 1
let tool2 = Tool(name: "pencil")
// name = pencil , 數量為默認值為 1
let tool3 = Tool(name: "ruler", quantity: 3)
// name = ruler , 數量為 3
結果如下: